﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using System.IO;
using Fixx.DTO;

namespace Fixx.Connector
{
    internal class FixxConnection : IFixxConnection
    {
		public static ManualResetEvent allDone = new ManualResetEvent(false);
		const int BUFFER_SIZE = 1024;

		private IFixxSettings _FixxSettings { get; set; }
		
        public FixxConnection(IFixxSettings settings)
        {
            this._FixxSettings = settings;
        }


        public void Get(string path, Action<FixxStatus> callback)
        {
			FixxState state = new FixxState()
			{
				Callback = callback,
				Connection = this.CreateConnection(path)
			};
			this.BeginGetResponse(state);
        }
		public void Post(string path,IFixxDalObject fixxObject, Action<FixxStatus> callback)
		{
			this._PutOrPost("POST", path, fixxObject, callback);
		}
		public void Put(string path, IFixxDalObject fixxObject, Action<FixxStatus> callback)
		{
			this._PutOrPost("PUT", path, fixxObject, callback);
		}

		private void _PutOrPost(string method,string path, IFixxDalObject fixxObject, Action<FixxStatus> callback)
		{
			FixxState state = new FixxState()
			{
				Callback = callback,
				Connection = this.CreateConnection(path),
				PostData = fixxObject.ToXmlString()
			};
			state.Connection.Method = method;

			state.Connection.BeginGetRequestStream(this.PostStreamOpened, state);
		}
		private void PostStreamOpened(IAsyncResult result)
		{
			var state = (FixxState)result.AsyncState;

			Stream postStream = state.Connection.EndGetRequestStream(result);

			UTF8Encoding encoding = new UTF8Encoding();
			byte[] bytes = encoding.GetBytes(state.PostData);
			//state.Connection.ContentLength = bytes.Length;

			postStream.Write(bytes, 0, bytes.Length);
			postStream.Close();
			this.BeginGetResponse(state);

		}

		private void BeginGetResponse(FixxState state)
		{
			try
			{
				IAsyncResult result = (IAsyncResult)state.Connection.BeginGetResponse(
					new AsyncCallback(GetResponseHandle),
					state
				);
				ThreadPool.RegisterWaitForSingleObject(
					result.AsyncWaitHandle,
					new WaitOrTimerCallback(TimeoutCallback),
					state.Connection,
					this._FixxSettings.TimeOut,
					true
				);

			}
			catch (WebException e)
			{
				state.RaiseCallBack(e);
			}
			catch (Exception e)
			{
				state.RaiseCallBack(e);
			}
		}


		private static void TimeoutCallback(object state, bool timedOut)
		{
			if (timedOut)
			{
				HttpWebRequest request = state as HttpWebRequest;
				if (request != null)
				{
					request.Abort();
					allDone.Set();
					
				}
			}
		}


        private void GetResponseHandle(IAsyncResult result)
        {
			var state = (FixxState)result.AsyncState;
			try
			{
				var conn = state.Connection;
				state.Response = (HttpWebResponse)conn.EndGetResponse(result);

				Stream responseStream = state.Response.GetResponseStream();

				state.StreamResponse = responseStream;

				IAsyncResult readResult = responseStream.BeginRead(
					state.BufferRead, 
					0, 
					BUFFER_SIZE, 
					new AsyncCallback(ReadCallBack), 
					state
				);
				return;
			}
			catch (Exception e)
			{
				state.RaiseCallBack(e);
			}
        }
		private static void ReadCallBack(IAsyncResult result)
		{
			var state = (FixxState)result.AsyncState;
			try
			{
				var conn = state.Connection;

				Stream responseStream = state.StreamResponse;
				int read = responseStream.EndRead(result);
				if (read > 0)
				{
					state.RequestData.Append(Encoding.ASCII.GetString(state.BufferRead, 0, read));
					IAsyncResult asynchronousResult = responseStream.BeginRead(
						state.BufferRead, 0, BUFFER_SIZE, new AsyncCallback(ReadCallBack), state);
					return;
				}
				else
				{
					state.Response.Close();
					state.RaiseCallBack();
				}

			}
			catch (WebException e)
			{
				state.RaiseCallBack(e);
			}
			catch (Exception e)
			{
				state.RaiseCallBack(e);
			}
			allDone.Set();

		}


        private HttpWebRequest CreateConnection(string path)
        {
            var url = this._CreateUriString(path);
            HttpWebRequest myReq = (HttpWebRequest)WebRequest.Create(url);
            if (this._FixxSettings.TransportLayer == TransportLayer.XML)
            {
				myReq.Accept = "application/xml";
				myReq.ContentType = "application/xml";
			}
			else
			{
				myReq.Accept = "application/json";
				myReq.ContentType = "application/json";
			}

            string username = this._FixxSettings.Username;
            string password = this._FixxSettings.Password;
            string usernamePassword = username + ":" + password;
            CredentialCache mycache = new CredentialCache();
            mycache.Add(new Uri(url), "Basic", new NetworkCredential(username, password));
            myReq.Credentials = mycache;
            myReq.Headers.Add("Authorization", "Basic " + Convert.ToBase64String(new ASCIIEncoding().GetBytes(usernamePassword)));
			return myReq;
        }
        
        private string _CreateUriString(string path)
        {
            var s = this._FixxSettings;
            if (!path.StartsWith("/"))
                path = "/" + path;
            return string.Format("http://{0}:{1}/api{2}", s.Host,s.Port,path);
        }
       
    }
}
